<template>
  <div class="dg" ref="dgInstance">
    <d-g-header ref="dgHeaderInstance"
                :style="{paddingRight: scrollBarWidth + 'px'}"
                :settings="headerSettings"
                :width="width"
                :root-config="rootConfig"
                :cur-sorting="curSorting"
                @sort-change = "handleSortChange"
                @check-all="handleHeadCheckAllChange"
                @col-resize="handleColResize"></d-g-header>
    <d-g-body ref="dgBodyInstance"
              :leftFixedLeafColumns="leftFixedLeafColumns"
              :rightFixedLeafColumns="rightFixedLeafColumns"
              :normalLeafColumns="normalLeafColumns"
              :width="width"
              :root-config="rootConfig"
              :is-loading="isLoading"
              :data-source="data"
              @resize="handleResize"
              @check-change="handleCheckChange"
              @selected-change="handleSelectedChange"
              @check-all="handleNodesCheckAllChange"
              @horizontal-scroll="handleHorizontalScroll"></d-g-body>
    <pagination ref="paginationInstance" v-if="config.pagination"
                :config="rootConfig.pagination"
                :is-loading="isLoading" @start-load-data="handleLoadData"></pagination>
    <column-filter :config="rootConfig" :columns="rootConfig.columns" @column-change="columnChange"></column-filter>
  </div>
</template>

<script>
  import elementResizeEvent from 'element-resize-event'
  import {localStorage} from '../libs/storage'
  import _ from 'lodash'
  import Vue from 'vue'
  import DGHeader from './header/DGHeader.vue'
  import DGBody from './body/DGBody.vue'
  import utils from './utils'
  import Pagination from './pagination/Pagination.vue'
  import Locales from './locales'
  import ColumnFilter from './columnFileter/ColumnFilter.vue'

  let DG_MIN_COLUMN_WIDTH = 80
  let COL_SETTINGS_PREFIX = 'va-dg-col-settings-'

  let initColumns = function (columns, parentColumn, leafColumns, groupColumns) {
    let maxDepth = 1
    let firstRightFixedColumn = 0
    for (let idx in columns) {
      if (columns.hasOwnProperty(idx)) {
        columns[idx].parent = parentColumn
        columns[idx]._rowIndex = parentColumn ? parentColumn._rowIndex + 1 : 0
        columns[idx].hide = (parentColumn && parentColumn.hide === true) || columns[idx].hide === true
        if (columns[idx]._rowIndex + 1 > maxDepth) {
          maxDepth = columns[idx]._rowIndex + 1
        }
        // 上、右、下、左
        columns[idx].borders = [0, 0, 0, 0]
        if (parentColumn) {
          columns[idx].borders[0] = 1
        }
        columns[idx].borders[1] = 1
        // 右边第一层第一列 或有左边框的第一列 左边框
        if (((!parentColumn && columns[idx].fixed === 'right') || (parentColumn && parentColumn.borders[3])) &&
          !firstRightFixedColumn) {
          columns[idx].borders[3] = 1
          firstRightFixedColumn = true
        }
        if (columns[idx].subColumns && columns[idx].subColumns.length > 0) {
          let depth = initColumns(columns[idx].subColumns, columns[idx], leafColumns, groupColumns)
          if (depth > maxDepth && columns[idx].hide !== true) {
            maxDepth = depth
          }
          groupColumns.push(columns[idx])
        } else {
          columns[idx].borders[2] = 1
          leafColumns.push(columns[idx])
        }
      }
    }
    return maxDepth
  }

  let calcColumnLeft = function (columns) {
    let curLeft = 0
    _.forEach(columns, item => {
      item._left = curLeft
      curLeft += item._width
      if (item.subColumns && item.subColumns.length > 0) {
        calcColumnLeft(item.subColumns)
      }
    })
  }

  let calcColumnPosition = function ([
                                       leftFixedColumns,
                                       normalColumns,
                                       rightFixedColumns
                                     ], [
                                       leftFixedLeafColumns,
                                       normalLeafColumns,
                                       rightFixedLeafColumns
                                     ], groupColumns, tableWidth, headerInstance, minColumnWidth, refreshPosition = true) {
    let specWidth = 0
    let noWidthColumns = []
    let allLeafColumns = [].concat(leftFixedLeafColumns,
      normalLeafColumns,
      rightFixedLeafColumns)
    // 如果叶子标题栏设置了width，则宽度_width由width决定
    _.forEach(allLeafColumns, item => {
      if (item.hide) {
        item._width = 0
        return
      } else if (item.width) {
        let width = +item.width || 0
        if (width) {
          specWidth += width
          item._width = width
          return
        }
      }
      noWidthColumns.push(item)
    })
    // 如果指定了宽度的列总宽度 小于 表的显示宽度，则没有指定宽度的叶子标题栏将平分此宽度
    if (noWidthColumns.length > 0) {
      let width
      width = _.max([Math.floor((tableWidth - specWidth) / noWidthColumns.length), minColumnWidth])
      let totalWidth = 0
      _.forEach(noWidthColumns, item => {
        if ((!item.subColumns || item.subColumns.length === 0) && item.checkbox && width < minColumnWidth + 30) {
          item._width = minColumnWidth + 30
        } else {
          item._width = width
        }
        totalWidth += item._width
      })
      if (tableWidth > totalWidth + specWidth) {
        noWidthColumns[noWidthColumns.length - 1]._width += tableWidth - specWidth - totalWidth
      }
    }

    _.forEach(groupColumns, item => {
      let width = 0
      _.forEach(item.subColumns, col => {
        width += col._width || 0
      })
      item._width = width
    })
    calcColumnLeft(leftFixedColumns)
    calcColumnLeft(normalColumns)
    calcColumnLeft(rightFixedColumns)
    if (refreshPosition) {
      _.forEach(groupColumns, item => {
        item._$headerVM && item._$headerVM.refreshPosition()
      })
      _.forEach(allLeafColumns, item => {
        item._$headerVM && item._$headerVM.refreshPosition()
      })
      headerInstance && headerInstance.refreshPosition()
    }
  }

  export default {
    components: {DGHeader, DGBody, Pagination, ColumnFilter},
    props: {
      config: {
        type: Object,
        required: true
      }
    },
    data () {
      return {
        checkChangeDebounce: _.debounce(function ($this, data) {
          $this.$emit('check-change', data)
        }, 100, {leading: false}),
        selectedChangeDebounce: _.debounce(function ($this, data) {
          $this.$emit('selected-change', data)
        }, 100, {leading: false}),

        rootConfig: {},
        data: [],
        width: 0,
        height: 0,
        headerSettings: {
          heights: [],
          leftFixedColumns: [],
          rightFixedColumns: [],
          normalColumns: []
        },
        leftFixedLeafColumns: [],
        rightFixedLeafColumns: [],
        normalLeafColumns: [],
        groupColumns: [],
        insideContainerStyle: null,
        scrollBarWidth: utils.scrollBarWidth,
        isLoading: false,
        curSorting: null,
        saveState: _.debounce(() => {
          if (this.key) {
            let key = `${COL_SETTINGS_PREFIX}${this.key}`
            localStorage.set(key, [
              this.getColumnState(this.headerSettings.leftFixedColumns),
              this.getColumnState(this.headerSettings.normalColumns),
              this.getColumnState(this.headerSettings.rightFixedColumns)
            ])
          }
        }, 1000, {leading: false}),
        setCheckAllState: _.debounce(isAllChecked => {
          _.forEach([].concat(this.leftFixedLeafColumns,
            this.normalLeafColumns,
            this.rightFixedLeafColumns), item => {
            item._$headerVM && item._$headerVM.setCheckAllState(isAllChecked)
          })
        }, 100)
      }
    },
    computed: {
      locale () {
        return Locales.ZH_CN
      },
      key () {
        return (this.config && this.config.key) || null
      }
    },
    methods: {
      getColumnState (cols) {
        let colsState = []
        for (let col of cols) {
          colsState.push({
            field: col.field,
            width: col.width,
            hide: col.hide,
            sort: this.curSorting && this.curSorting.column === col ? this.curSorting.sort : undefined,
            subColumns: col.subColumns ? this.getColumnState(col.subColumns) : undefined
          })
        }
        return colsState
      },
      setColumnState (cols, colsState) {
        if (!cols || !colsState || cols.length !== colsState.length) return
        for (let i = 0; i < cols.length; i++) {
          if (colsState[i].sort) {
            this.curSorting = {column: cols[i], sort: colsState[i].sort}
          }
          if (colsState && colsState[i] && cols[i].field === colsState[i].field) {
            colsState[i].width && Vue.set(cols[i], 'width', colsState[i].width)
            colsState[i].hide !== undefined && Vue.set(cols[i], 'hide', colsState[i].hide)
          }
          if (cols[i].subColumns && colsState[i].subColumns) {
            this.setColumnState(cols[i].subColumns, colsState[i].subColumns)
          }
        }
      },
      restoreState () {
        if (this.key) {
          let key = `${COL_SETTINGS_PREFIX}${this.key}`
          let cs = localStorage.get(key)
          if (cs && cs.length === 3) {
            this.setColumnState(this.headerSettings.leftFixedColumns, cs[0])
            this.setColumnState(this.headerSettings.normalColumns, cs[1])
            this.setColumnState(this.headerSettings.rightFixedColumns, cs[2])
          }
        }
      },
      columnChange () {
        this.saveState()
        this.resize()
      },
      setPagination (pageIndex, pageSize) {
        if (this.$refs.paginationInstance) {
          this.$refs.paginationInstance.pageIndex = pageIndex
          this.$refs.paginationInstance.pageIndex = pageSize
          return true
        }
        return false
      },
      reload () {
        let pageIndex
        let pageSize
        if (this.$refs.paginationInstance) {
          pageIndex = this.$refs.paginationInstance.pageIndex
          pageSize = this.$refs.paginationInstance.pageSize
        }
        return this.handleLoadData(pageIndex, pageSize)
      },
      handleSortChange (column, sort) {
        if (this.rootConfig.sortRemote !== false && this.curSorting && this.curSorting.column === column && this.curSorting.sort === sort) {
          this.curSorting = null
        } else {
          _.forEach(this.leftFixedLeafColumns, item => {
            if (item !== column && item._$headerVM) {
              item._$headerVM.sortClass = null
            }
          })
          _.forEach(this.rightFixedLeafColumns, item => {
            if (item !== column && item._$headerVM) {
              item._$headerVM.sortClass = null
            }
          })
          _.forEach(this.normalLeafColumns, item => {
            if (item !== column && item._$headerVM) {
              item._$headerVM.sortClass = null
            }
          })
          this.curSorting = sort ? {column, sort} : null
        }
        this.reload()
        this.saveState()
      },
      handleLoadData (pageIndex, pageSize) {
        let ds = this.rootConfig.dataSource
        if (ds instanceof Function) {
          let sorting = null
          if (this.curSorting) {
            sorting = {field: this.curSorting.column.field, sort: this.curSorting.sort}
          }
          ds = ds(pageIndex, pageSize, sorting)
        }
        if (ds && ds.then && ds.then instanceof Function) {
          this.isLoading = true
          return ds.then(data => {
            let tmp
            if (data instanceof Array) {
              tmp = data
            } else {
              tmp = data.data
              this.$refs.paginationInstance && this.$refs.paginationInstance.loadComplete(data.totalCount)
            }
            if (this.curSorting && this.rootConfig.sortRemote === false) {
              tmp = _.orderBy(tmp, [this.curSorting.column.field], [this.curSorting.sort])
            }
            this.data = tmp
          })['finally'](() => {
            this.isLoading = false
          })
        } else if (ds instanceof Array) {
          if (this.curSorting) {
            ds = _.orderBy(ds, [this.curSorting.column.field], [this.curSorting.sort])
          }
          this.data = ds
        } else {
          this.data = []
        }
      },
      initConfig: _.debounce(($this, config) => {
        let scrollIns = $this.$refs.dgBodyInstance && $this.$refs.dgBodyInstance.$refs.dgScrollAreaInstance
        let scrollWidth = 0
        if (scrollIns) {
          scrollWidth = scrollIns.scrollHeight > scrollIns.clientHeight ? utils.scrollBarWidth : 0
        }
        $this.width = $this.$refs.dgInstance.clientWidth - scrollWidth
        $this.height = $this.$refs.dgInstance.clientHeight
        $this.isLoading = false
        let nConfig = _.cloneDeep(config)
        if (!nConfig.columns) {
          console.error('data-grid must config columns.')
          return
        }
        if (!nConfig.locale) {
          nConfig.locale = $this.locale
          nConfig.pagination && (nConfig.pagination.locale = $this.locale)
        }
        $this.rootConfig = nConfig
        if (!$this.rootConfig.minColumnWidth) {
          $this.rootConfig.minColumnWidth = DG_MIN_COLUMN_WIDTH
        }
        let leftFixedLeafColumns = []
        let rightFixedLeafColumns = []
        let normalLeafColumns = []
        let newGroupColumns = []
        let depth = _.max([
          initColumns(_.filter(nConfig.columns, item => { return item.fixed === 'left' }), null, leftFixedLeafColumns, newGroupColumns),
          initColumns(
            _.filter(nConfig.columns, item => { return !item.fixed || (item.fixed !== 'left' && item.fixed !== 'right') }),
            null,
            normalLeafColumns,
            newGroupColumns),
          initColumns(_.filter(nConfig.columns, item => { return item.fixed === 'right' }), null, rightFixedLeafColumns, newGroupColumns)])
        let heights = []
        for (let i = depth - 1; i >= 0; i--) {
          heights[i] = (nConfig.headerHeights && nConfig.headerHeights[i]) || 24
          if (i < depth - 1) {
            heights[i] += heights[i + 1]
          }
        }
        $this.headerSettings = {
          heights,
          leftFixedColumns: [],
          rightFixedColumns: [],
          normalColumns: []
        }
        $this.leftFixedLeafColumns = leftFixedLeafColumns
        $this.rightFixedLeafColumns = rightFixedLeafColumns
        $this.normalLeafColumns = normalLeafColumns
        $this.groupColumns = newGroupColumns

        _.forEach(nConfig.columns, item => {
          if (item.fixed === 'left') {
            $this.headerSettings.leftFixedColumns.push(item)
          } else if (item.fixed === 'right') {
            $this.headerSettings.rightFixedColumns.push(item)
          } else {
            $this.headerSettings.normalColumns.push(item)
          }
        })

        $this.restoreState()

        $this.resize(false)

        $this.refreshLayout = _.debounce(() => {
          let scrollIns = $this.$refs.dgBodyInstance && $this.$refs.dgBodyInstance.$refs.dgScrollAreaInstance
          let scrollWidth = 0
          if (scrollIns) {
            scrollWidth = scrollIns.scrollHeight > scrollIns.clientHeight ? utils.scrollBarWidth : 0
          }
          let curWidth = $this.$refs.dgInstance.clientWidth - scrollWidth
          let curHeight = $this.$refs.dgInstance.clientHeight
          $this.width = curWidth
          $this.height = curHeight
          $this.resize()
        }, 200, {leading: false, maxWait: 300})
        Vue.nextTick(() => {
          $this.refreshLayout()
          elementResizeEvent($this.$refs.dgInstance, $this.refreshLayout)
          $this.rootConfig.autoLoad && $this.reload()
        })
      }, 100, {leading: true}),
      resize (refreshPosition = true) {
        calcColumnPosition([
          this.headerSettings.leftFixedColumns,
          this.headerSettings.normalColumns,
          this.headerSettings.rightFixedColumns
        ], [
          this.leftFixedLeafColumns,
          this.normalLeafColumns,
          this.rightFixedLeafColumns
        ], this.groupColumns, this.width, this.$refs.dgHeaderInstance, this.rootConfig.minColumnWidth, refreshPosition)
        this.saveState()
        this.$refs.dgBodyInstance && this.$refs.dgBodyInstance.refreshPosition()
      },
      handleResize () {
        this.refreshLayout && this.refreshLayout()
      },
      handleColResize () {
        this.resize()
      },
      handleHorizontalScroll (offset) {
        this.$refs.dgHeaderInstance.setNormalColumnsScroll(offset)
      },
      handleHeadCheckAllChange (isAllChecked) {
        this.$refs.dgBodyInstance.setCheckAllstate(isAllChecked)
      },
      handleNodesCheckAllChange (isAllChecked) {
        this.setCheckAllState(isAllChecked)
      },
      handleSelectedChange (data) {
        this.selectedChangeDebounce(this, data)
      },
      handleCheckChange (data) {
        this.checkChangeDebounce(this, data)
      }
    },
    watch: {
      config (val) {
        this.initConfig(this, val)
      },
      data () {
        this.refreshLayout && this.refreshLayout()
      }
    },
    mounted () {
      this.initConfig(this, this.config)
    },
    updated () {
      Vue.nextTick(() => {
        this.resize()
      })
    },
    beforeDestroy () {
      elementResizeEvent.unbind(this.$refs.dgInstance, this.refreshLayout)
    },
    destroyed () {
    }
  }
</script>
